home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Fatted Calf
/
The Fatted Calf.iso
/
Applications
/
Games
/
Tetris
/
Source
/
TetMatrix.m
< prev
next >
Wrap
Text File
|
1975-04-26
|
13KB
|
570 lines
#import <appkit/NXImage.h>
#import <ctype.h>
#import <dpsclient/wraps.h>
#import <math.h>
#import <appkit/graphics.h>
#import "TetMatrix.h"
#import "Piece.h"
#import "ScoreKeeper.h"
#import "TetApp.h"
#import "wraps.h"
// The number of pixels a block moves every step
// Make it a divisor of the block size
static const float ANM_DELTA = 8.0;
const float GAMEOVER_BMAP_H = 100.0;
extern long random();
extern void srandom(int seed);
extern int getpid();
@implementation TetMatrix
/*
* Some things should be set differently for color machines for either
* performances or aesthetic reasons.
*/
- initForColor
{
const NXScreen *deepestScreen;
deepestScreen=[NXApp colorScreen];
if ( deepestScreen->depth == NX_TwoBitGrayDepth ) {
anm_gameover_delta = 1.0;
} else {
anm_gameover_delta = 2.0;
}
return self;
}
- initFrame:(const NXRect *)frameRect
{
NXSize size, interCell;
extern BOOL resize(NXSize *aSize);
float viewWidth, viewHeight;
// Set the # of rows and columns in the view
[super initFrame:frameRect numRows:TETRIS_ROWS numCols:TETRIS_COLUMNS];
[self setBackgroundGray:NX_BLACK];
size.width = size.height = 6.0;
[self setInset:&size];
// Set the amount of space to leave b/w each block
interCell.width = interCell.height = 1.0; // 1 pixel
[self setIntercell:&interCell];
pieceVisible = active = NO;
thePiece = [[Piece alloc] init];
[thePiece getBlockSize:&size];
// Should only resize in Piece.m
resize(&size); // Make sure block image isn't too large
[super setElementSize:&size];
// Size Game View.
// Default view size is (182, 438)
// Getting (176, 416)
viewWidth = 6. + TETRIS_COLUMNS * (size.width + interCell.width) + 6.;
viewHeight = 6. + (TETRIS_ROWS)* (size.height + interCell.height) + 6.;
#ifdef DEBUG
fprintf(stderr, "Sizing the Tetris view to (%f,%f)\n", viewWidth, viewHeight);
#endif
[self sizeTo: viewWidth :viewHeight];
scoreKeeper = nil;
anmBitmap = [[NXImage allocFromZone:[self zone]] initSize:&bounds.size];
// Eventually want the background to be setable
// anmBitmap = [NXImage initSize:&bounds.size];
// [anmBitmap useFromFile:"Snow.tiff"];
showNext = nil;
[self initForColor];
gameRunning = NO;
srandom(getpid()); // Seed random # generator
return self;
}
- setupRandomFill:(int)max
{
int blockNum;
int row, col;
if (max > TETRIS_ROWS) max = TETRIS_ROWS;
if (max > 10) max = 10;
// fprintf (stderr, "Start random fill ...\n");
for (row = 0; row < max; row++) {
for (col = 0; col < TETRIS_COLUMNS; col++) {
if ((random () & 0x3) == 0) { /* set 1/4 */
blockNum = (random () & 0x3); // Rand num 0..3
[super setBitmap: [thePiece getBlockImage:blockNum] at:row :col];
}
}
}
return self;
}
- (BOOL)acceptsFirstResponder
{
return active;
}
- drawSelf:(const NXRect *)rects :(int)rectCount
{
[super drawSelf:rects :rectCount];
if (pieceVisible) {
NXRectClip(&insetBounds);
[thePiece draw:self];
}
return self;
}
/*
* Act upon keyboard input from the user.
*
*/
- keyDown:(NXEvent *)theEvent
{
unsigned short charCode;
charCode = theEvent->data.key.charCode;
if (isupper(charCode))
charCode = tolower(charCode);
switch (charCode) {
case 'k':
case '5':
if (charCode == 'k' || theEvent->flags & NX_NUMERICPADMASK)
[thePiece turn:self];
break;
case 'j':
case '4':
if (charCode == 'j' || theEvent->flags & NX_NUMERICPADMASK)
[thePiece left:self];
break;
case 'l':
case '6':
if (charCode == 'l' || theEvent->flags & NX_NUMERICPADMASK)
[thePiece right:self];
break;
case ' ':
case '2':
case '0':
if (charCode == ' ' || theEvent->flags & NX_NUMERICPADMASK) {
[self stopTimedEntry];
[thePiece drop:self];
[self startTimedEntry];
}
break;
case 13: // Pause if the user hits return
[NXApp pause:self];
}
return self;
}
- setPieceVisible:(BOOL)flag
{
pieceVisible = flag;
return self;
}
- setScoreKeeper:keeper
{
scoreKeeper = keeper;
return self;
}
/*
* Determine how fast a piece will drop.
*/
-(double) newDelay
{
return 0.5 - 0.1 * sqrt(2.5 * level);
}
- newGame:(int)theLevel
{
[[super setBitmap:nil] display];
[window makeFirstResponder:self];
[self setPieceVisible:YES];
[thePiece reset:self
piece:((showNext) ? [showNext pieceInfo] : PIECE_INFO_NULL)];
[self setupRandomFill:[randomFields intValue]];
[self display];
level = (float)theLevel;
teDelay = [self newDelay];
[self startTimedEntry];
active = YES;
return self;
}
- pause:sender
{
// Make TetApp the first responder
[window makeFirstResponder:window];
// Will making this object the first responder cause problems?
// [window makeFirstResponder:self];
active = NO;
return [self stopTimedEntry];
}
- continue:sender
{
active = YES;
[window makeFirstResponder:self];
return [self startTimedEntry];
}
- stop:sender
{
active = NO;
[window makeFirstResponder:window];
return [self stopTimedEntry];
}
/*
* Scroll "Game Over" down the screen after the game is finished.
*/
- animateGameOver
{
NXSize tmpSize;
id anmGameOver;
NXRect destRect, unionRect;
tmpSize.height = GAMEOVER_BMAP_H;
tmpSize.width = insetBounds.size.width;
anmGameOver = [[NXImage alloc] initSize:&tmpSize];
// Composite "Game Over" to a window
[anmGameOver lockFocus];
PSsetalpha(0.0);
PSrectfill(0.0, 0.0, insetBounds.size.width, GAMEOVER_BMAP_H);
PSsetalpha(1.0);
PSgameover(insetBounds.size.width);
[anmGameOver unlockFocus];
[super getRect:&destRect for:(int)(TETRIS_ROWS / 2) :0];
[super getRect:&unionRect for:TETRIS_ROWS - 1 :TETRIS_COLUMNS - 1];
NXUnionRect(&unionRect, &destRect);
[anmBitmap lockFocus];
[self drawSelf:&destRect :1];
[anmBitmap unlockFocus];
NXSetRect(&unionRect, destRect.origin.x, unionRect.origin.y,
destRect.size.width, GAMEOVER_BMAP_H);
[self lockFocus];
NXRectClip(&destRect);
while (unionRect.origin.y - anm_gameover_delta >= destRect.origin.y) {
unionRect.origin.y -= anm_gameover_delta;
[anmGameOver composite:NX_SOVER toPoint:&unionRect.origin];
[[self window] flushWindow];
NXPing();
// Redraw the background to erase the "Game Over" letters
// [anmBitmap composite:NX_COPY fromRect:&unionRect toPoint:&unionRect.origin];
[anmBitmap composite:NX_SOVER fromRect:&unionRect toPoint:&unionRect.origin];
NXPing();
}
[anmGameOver composite:NX_SOVER toPoint:&unionRect.origin];
[self unlockFocus];
[anmGameOver free];
return self;
}
/* The following methods:
* Animate a piece dropping.
* Dissolve filled rows.
* Removes filled rows from the game.
*/
- (BOOL) rowFilled:(int)row
{
int xc;
for (xc = 0; xc < TETRIS_COLUMNS; xc++)
if (![super bitmapAt:row :xc])
break;
return (xc == TETRIS_COLUMNS);
}
- (BOOL) rowEmpty:(int) row
{
int xc;
for (xc = 0; xc < TETRIS_COLUMNS; xc++)
if ([super bitmapAt:row :xc])
break;
return (xc == TETRIS_COLUMNS);
}
- fadeFilledRows:(int) from :(int) to
{
float gc;
NXRect destRect;
NXRect unionRect;
[super getRect:&destRect for:from :0];
[super getRect:&unionRect for:to :TETRIS_COLUMNS - 1];
NXUnionRect(&unionRect, &destRect);
// Composite the rows we are about to fade to anmBitmap
[anmBitmap lockFocus];
[self drawSelf:&destRect :1];
[anmBitmap unlockFocus];
// Fade the blocks
[self lockFocus];
for (gc = 1.0; gc > 0.3; gc -= .03) {
PSsetgray(gc);
PScompositerect(destRect.origin.x, destRect.origin.y, destRect.size.width,
destRect.size.height, NX_PLUSD);
[[self window] flushWindow];
// Now redraw the blocks so we can fade them a little more
// [anmBitmap composite:NX_COPY fromRect:&destRect toPoint:&destRect.origin];
[anmBitmap composite:NX_SOVER fromRect:&destRect toPoint:&destRect.origin];
NXPing();
}
// Now make the blocks totally disappear.
PSsetgray(NX_BLACK);
PScompositerect(destRect.origin.x, destRect.origin.y, destRect.size.width,
destRect.size.height, NX_SOVER);
// destRect.size.height, NX_COPY);
[self unlockFocus];
return self;
}
/*
* Move the pieces as they are moved down on the screen after one or more rows
* has been cleared.
*/
- animateDrop:(int) firstFullRow :(int)firstNonFullRow :(int) lastNonEmptyRow
{
NXPoint dPt;
NXRect destRect;
NXRect unionRect;
[super point:&dPt for:firstFullRow :0]; // Place where to put falling blocks
[super getRect:&destRect for:firstNonFullRow :0]; // First row to move down
// Last row to move down
[super getRect:&unionRect for:lastNonEmptyRow :TETRIS_COLUMNS - 1];
// We want to move everything down from the last non-full row to
// the last row with a piece in it.
NXUnionRect(&unionRect, &destRect);
unionRect = destRect;
// Create the image of the blocks
[anmBitmap lockFocus];
[self drawSelf:&destRect :1];
[anmBitmap unlockFocus];
[self lockFocus];
PSsetgray(NX_BLACK); // The destination Rect is Black
PScompositerect(destRect.origin.x, destRect.origin.y, destRect.size.width,
destRect.size.height, NX_COPY);
while (unionRect.origin.y - ANM_DELTA > dPt.y) {
unionRect.origin.y -= ANM_DELTA;
// First clear the window
[anmBitmap composite:NX_COPY fromRect:&destRect toPoint:&unionRect.origin];
[[self window] flushWindow];
PScompositerect(unionRect.origin.x, unionRect.origin.y, unionRect.size.width,
unionRect.size.height, NX_COPY);
NXPing();
}
[anmBitmap composite:NX_COPY fromRect:&destRect toPoint:&dPt];
[[self window] flushWindow];
// NXPing();
[self unlockFocus];
return self;
}
/*
* Remove the filled rows after they have dissolved
*/
- removeFilledRows
{
BOOL filled;
int ycfrom, ycto, yctop;
int xc, yc;
int lastFilledRow, lastNonEmptyRow, firstFilled;
int piecey = [thePiece getCurRow]; // Get the bottom-most row of the piece.
do {
filled = NO; // This should only execute once?!?!
// Work our way up from the current piece's row.
for (ycfrom = piecey; ycfrom < piecey + MAX_SHAPE_SIZE; ycfrom++) {
if ([self rowFilled: ycfrom]) {
filled = YES;
firstFilled = ycfrom;
break;
}
}
if (filled) {
// Find the first row that isn't completely filled.
lastFilledRow = firstFilled; // Loop can execute 0 times
for (ycto = firstFilled + 1; ycto < firstFilled + MAX_SHAPE_SIZE; ycto++) {
if ([self rowFilled: ycto]) {
lastFilledRow = ycto;
} else {
break;
}
}
// Find the topmost row in the program. A row with at least one block.
// Assert: Must enter loop at least once.
for (yctop = lastFilledRow+1; yctop < TETRIS_ROWS; yctop++) {
if (! [self rowEmpty: yctop]) {
lastNonEmptyRow = yctop; // But do I have to execute this once?
} else {
break;
}
}
[self fadeFilledRows: firstFilled :lastFilledRow];
// Animate the all of the pieces as the move down.
[self animateDrop:firstFilled :lastFilledRow+1 :lastNonEmptyRow];
// Move the rows above the faded rows down to take their place.
for (yc = lastFilledRow+1; yc <=lastNonEmptyRow; yc++) {
for (xc = 0; xc < TETRIS_COLUMNS; xc++) // For each block in the row.
[super setBitmap:[self bitmapAt:yc :xc] at:firstFilled :xc];
firstFilled++;
}
// Clear the rows at the very top that have been moved down
// and which have nothing to take their place.
for (yc = firstFilled; yc <=lastNonEmptyRow; yc++) {
#ifdef DEBUG
printf("Removing row %d\n", yc);
#endif
for (xc = 0; xc < TETRIS_COLUMNS; xc++)
[super setBitmap:nil at:yc :xc];
}
piecey++;
}
} while (filled);
return self;
}
/* The main animation routines follow. A timed entry routine is setup
* so that it calls teHandler at a regular interval (depending on the
* level)
*/
/*
* The timed entry handler that gets called every teDelay seconds.
*
*/
- step
{
static int downWait = 0;
// Move the piece down. If it can't move down make it stick,
// remove filled rows, and generate a new piece.
if (![thePiece down:self]) {
// After the piece sticks to the bottom, wait for a while before
// we send the next piece. The wait depends on the current level.
if (++downWait >= level) { // Don't commit the piece immediately
downWait = 0;
[self stopTimedEntry];
[self setPieceVisible:NO];
[thePiece stick:self]; // Make the piece stick
[self removeFilledRows];
[self setPieceVisible:YES];
if (scoreKeeper) {
[scoreKeeper addScore:[thePiece points]];
level += (MAX_LEVEL - level) / 400.0;
teDelay = [self newDelay];
}
// show the next piece.
if ([thePiece reset:self piece:((showNext) ?
[showNext pieceInfo] : PIECE_INFO_NULL)])
[self startTimedEntry];
else {
[self animateGameOver];
[[self window] makeFirstResponder:[self window]];
active = NO;
[NXApp gameOver];
}
}
} else { // The piece can't go down
downWait = 0; // Set a delay before showing next piece
}
return self;
}
static void runOneStep (DPSTimedEntry timedEntry, double timeNow, void *data)
{
[(id)data step];
}
- startTimedEntry
{
if (!gameRunning) {
timedEntryNum = DPSAddTimedEntry(teDelay,&runOneStep,
self, NX_BASETHRESHOLD);
gameRunning = YES;
}
return self;
}
- stopTimedEntry
{
if (gameRunning) {
DPSRemoveTimedEntry(timedEntryNum);
gameRunning = NO;
}
return self;
}
/*
* Clean up.
*/
- free
{
[thePiece free];
[anmBitmap free];
return [super free];
}
@end